/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package SocialMatrixPackage;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Vector;

import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;

import multiarray.MultiArray;

/**
 * Stores the social values.  The outter array is the source index
 * The inner array is how the source feels about the target.
 * This currently does not handle heirarchies.
 * This can be used for grid and group.
 * @author Evan
 */
public class SocialMatrix
{
  // stores the dimensions of the multiarray representing the ascriptive characteristics
  // note that there may be an updated version of leo's stuff that handles heirarchies
  private int[] dimensions;
  // these are the values themselves
  private MultiArray<MultiArray<Double>> socialValues;
  // names of the ascriptives.
  private Vector<Vector<String>> stringNames = new Vector<Vector<String>>();
  // the tree where all the names, etc are derived
  private JTree ascriptiveTree;
  // The names of the ascriptive categories
  private Vector<String> ascriptiveCategories = new Vector<String>();
  // every cross-section
  private Vector<int[]> allCombinations;
  // the year of these values
  private int year = 0;

  public int[] getDimensions()
  {
    return dimensions;
  }

  /**
   * @return the altruismValues
   */
  public MultiArray<MultiArray<Double>> getSocialValues()
  {
    return socialValues;
  }

  /**
   * @return the stringNames
   */
  public Vector<Vector<String>> getStringNames()
  {
    return stringNames;
  }

  /**
   * @return the ascriptiveTree
   */
  public JTree getAscriptiveTree()
  {
    return ascriptiveTree;
  }

  /**
   * @return the allCombinations
   */
  public Vector<int[]> getAllCombinations()
  {
    return allCombinations;
  }

  public int getYear()
  {
    return year;
  }

  // constructors or factories
  public SocialMatrix(JTree ascriptiveTree)
  {
    setup(ascriptiveTree);
  }

  // returns a new group altruism matrix based on the provided file
  //public static GroupAltruismMatrix getGroupAltruismMatrixFromFile(String fileName)
  //{
  //}
  public void setYear(int newYear)
  {
    year = newYear;
  }

  // sets up everything based on the provided altruism tree.
  // creates altruismValues multiarray
  // creates stringnames vector
  public void setup(JTree ascriptiveTree)
  {
    this.ascriptiveTree = ascriptiveTree;
    ascriptiveCategories.clear();

    // loop through the altruism tree to get individual ascriptive cats
    DefaultMutableTreeNode root = (DefaultMutableTreeNode) ascriptiveTree.getModel().getRoot();

    // for each category...
    for (int i = 0; i < root.getChildCount(); i++)
    {
      Vector<String> categoryVector = new Vector<String>();
      DefaultMutableTreeNode child = (DefaultMutableTreeNode) root.getChildAt(i);
      ascriptiveCategories.add((String) child.getUserObject());

      // use BFS to flesh out tree
      Vector<DefaultMutableTreeNode> queue = new Vector<DefaultMutableTreeNode>();
      queue.add(child);
      boolean done = false;

      while (!done)
      {
        //pop top
        DefaultMutableTreeNode top = queue.get(0);
        queue.remove(top);

        //check if top is a leaf
        if (top.isLeaf())
        {
          categoryVector.add((String) top.getUserObject());
        }
        else // enqueue children
        {
          for (int j = 0; j < top.getChildCount(); j++)
          {
            queue.add((DefaultMutableTreeNode) top.getChildAt(j));
          }
        }

        if (queue.size() <= 0)
        {
          done = true;
        }
      }

      stringNames.add(categoryVector);
    }

    // now that stringNames are setup, we can setup the multiarray
    // this array defines the multiarray size
    dimensions = new int[stringNames.size()];

    for (int i = 0; i < dimensions.length; i++)
    {
      dimensions[i] = stringNames.get(i).size();
    }

    // now we define the multiarrays.
    allCombinations = new Vector<int[]>();
    // do first combination... essentiall seed the vector
    for (int i = 0; i < dimensions[0]; i++)
    {
      // this looks like {i, a, b, .... , c}
      int[] currentCombination = new int[dimensions.length];
      currentCombination[0] = i;
      allCombinations.add(currentCombination);
    }

    if (dimensions.length > 1)
    {
      for (int i = 1; i < dimensions.length; i++)
      {
        int dimSize = dimensions[i];

        // stores the new combinations
        Vector<int[]> newCombinations = new Vector<int[]>();

        // for every element in the i ascriptive...
        for (int j = 0; j < dimSize; j++)
        {
          // for every combination already in the list...
          for (int k = 0; k < allCombinations.size(); k++)
          {
            int[] newCombo = new int[dimensions.length];
            int[] allComboAtK = allCombinations.get(k);

            // copy allComboAtK to newCombo
            for (int l = 0; l < newCombo.length; l++)
            {
              newCombo[l] = allComboAtK[l];
            }

            newCombo[i] = j;
            newCombinations.add(newCombo);
          }
        }

        // new combinations now has all the current combinations
        allCombinations = newCombinations;
      }
    }

    // now w/ all the combinations, add in a multiarray for each
    socialValues = new MultiArray<MultiArray<Double>>(dimensions);
    for (int i = 0; i < allCombinations.size(); i++)
    {
      int[] currentCombo = allCombinations.get(i);

      MultiArray<Double> targetValues = new MultiArray<Double>(dimensions);
      // fill out all targetvalues w/ zeros
      for (int j = 0; j < allCombinations.size(); j++)
      {
        targetValues.setElementAtIndex(allCombinations.get(j), 0.0);
      }

      socialValues.setElementAtIndex(currentCombo, targetValues);
    }
  }

  // if an entry is not found, it gets -1, which means anything
  public int[] translateStringsIntoIndex(Vector<String> input)
  {
    int[] ret = new int[stringNames.size()];

    for (int i = 0; i < ret.length; i++)
    {
      // note: indexOf already returns -1 if not found, so ANYTHING_STRING
      // also works b/c presumably it will never be used
      ret[i] = stringNames.get(i).indexOf(input.get(i));
    }

    return ret;
  }

  // -1 is "ANYTHING"
  public Vector<String> translateIndexIntoStrings(int[] input)
  {
    Vector<String> ret = new Vector<String>();

    for (int i = 0; i < input.length; i++)
    {
      int category = input[i];
      if (category != -1)
      {
        ret.add(stringNames.get(i).get(category));
      }
      else
      {
        ret.add(ascriptiveCategories.get(i));
      }
    }

    return ret;
  }

  public String convertIndexToString(int[] index)
  {
    Vector<String> indexVector = translateIndexIntoStrings(index);

    String ret = "[";
    for (int i = 0; i < indexVector.size() - 1; i++)
    {
      ret += indexVector.get(i) + ", ";
    }
    ret += indexVector.get(indexVector.size() - 1) + "]";

    return ret;
  }

  // based on the provided string vector, return a vector of all groups represented by
  // the string vector.  (groups = cross-sections)
  public Vector<int[]> getRepresentedGroups(Vector<String> input)
  {
    Vector<int[]> theGroups = new Vector<int[]>();

    // first we find if any of these entries in the input has children and if they do,
    // we need to find them all
    Vector<Vector<String>> allChildren = new Vector<Vector<String>>();
    DefaultMutableTreeNode root = (DefaultMutableTreeNode) ascriptiveTree.getModel().getRoot();
    for (int i = 0; i < input.size(); i++)
    {
      String currentEntry = input.get(i);
      // this vector contains the children, but note that ONLY LEAVES ARE CONSIDERED
      Vector<String> children = new Vector<String>();

      // now we check the ascriptive tree if the currentEntry has children.
      // use BFS to find children
      DefaultMutableTreeNode child = (DefaultMutableTreeNode) root.getChildAt(i);
      DefaultMutableTreeNode entryNode = null;
      Vector<DefaultMutableTreeNode> queue = new Vector<DefaultMutableTreeNode>();
      queue.add(child);
      boolean done = false;

      // find the node that represents the currentEntry
      while (!done)
      {
        // pop
        DefaultMutableTreeNode popped = queue.get(0);
        queue.remove(popped);
        //System.out.println(popped.getUserObject());
        // check if popped is our node
        if (((String) popped.getUserObject()).equals(currentEntry))
        {
          done = true;
          entryNode = popped;
        }

        // push children
        for (int k = 0; k < popped.getChildCount(); k++)
        {
          queue.add((DefaultMutableTreeNode) popped.getChildAt(k));
        }
      }

      done = false;
      queue.clear();
      queue.add(entryNode);
      // find the children (leaves) of this node, if any
      while (!done)
      {
        // pop
        DefaultMutableTreeNode popped = queue.get(0);
        queue.remove(popped);
        //System.out.println(popped.getUserObject());
        // check if popped is our node
        if (popped.isLeaf())
        {
          children.add((String) popped.getUserObject());
        }

        // push children
        for (int k = 0; k < popped.getChildCount(); k++)
        {
          queue.add((DefaultMutableTreeNode) popped.getChildAt(k));
        }

        // we're done if there's nothing on the queue.
        if (queue.size() == 0)
        {
          done = true;
        }
      }

      allChildren.add(children);
    }

    // now that the children are all listed, expand all possiblities based on
    // the children.
    Vector<Vector<String>> allStringCombinations = new Vector<Vector<String>>();

    // expand children
    // seed the results w/ the first children
    Vector<String> firstChild = allChildren.get(0);
    for (int i = 0; i < firstChild.size(); i++)
    {
      Vector<String> combo = new Vector<String>();
      combo.add(firstChild.get(i));
      allStringCombinations.add(combo);
    }

    for (int i = 1; i < allChildren.size(); i++)
    {
      Vector<Vector<String>> newCombinations = new Vector<Vector<String>>();
      Vector<String> currentChildGroups = allChildren.get(i);
      for (int j = 0; j < allStringCombinations.size(); j++)
      {
        for (int k = 0; k < currentChildGroups.size(); k++)
        {
          Vector<String> aCombo = (Vector<String>) allStringCombinations.get(j).clone();
          aCombo.add(currentChildGroups.get(k));
          newCombinations.add(aCombo);
        }
      }
      allStringCombinations = newCombinations;
    }

    for (int i = 0; i < allStringCombinations.size(); i++)
    {
      /*
      // print the current combo
      String printS = "[";
      Vector<String> pCombo = allStringCombinations.get(i);
      for (int j = 0; j < pCombo.size()-1; j++)
      {
      printS += pCombo.get(j) + ", ";
      }
      printS += pCombo.get(pCombo.size()-1) + "]";
      System.out.println(printS);
       */

      theGroups.add(translateStringsIntoIndex(allStringCombinations.get(i)));
    }

    // w/ all the combinations, return
    return theGroups;
  }

  public void testPrint()
  {
    for (int i = 0; i < allCombinations.size(); i++)
    {
      int[] currentCombo = allCombinations.get(i);
      // TEST PRINTING
      Vector<String> testT = translateIndexIntoStrings(currentCombo);
      int[] reverseT = translateStringsIntoIndex(testT);
      String testPrint = "[";
      String testTPrint = "[";
      String reverseTPrint = "[";
      for (int j = 0; j < currentCombo.length - 1; j++)
      {
        testPrint += currentCombo[j] + ", ";
        testTPrint += testT.get(j) + ", ";
        reverseTPrint += reverseT[j] + ", ";
      }
      testPrint += currentCombo[currentCombo.length - 1] + "]";
      testTPrint += testT.get(currentCombo.length - 1) + "]";
      reverseTPrint += reverseT[currentCombo.length - 1] + "]";

      System.out.println(testPrint);
      System.out.println(testTPrint);
      System.out.println(reverseTPrint);

    // END OF TEST PRINTING
    }
  }

  // returns the altruism between source and target
  public double getAltruism(int[] source, int[] target)
  {
    MultiArray sourceFeelings = socialValues.getElementAtIndex(source);
    return (Double) sourceFeelings.getElementAtIndex(target);
  }

  // returns the altruism between a source and multiple targets
  public double getAverageAltruismMultipleTargets(int[] source, Vector<int[]> targets)
  {
    MultiArray sourceFeelings = socialValues.getElementAtIndex(source);

    double sum = 0;
    for (int i = 0; i < targets.size(); i++)
    {
      sum += (Double) sourceFeelings.getElementAtIndex(targets.get(i));
    }

    return sum / targets.size();
  }

  // returns the altruism between multiple sources and a single target
  public double getAverageAltruismMultipleSources(Vector<int[]> sources, int[] target)
  {
    double sum = 0;

    for (int i = 0; i < sources.size(); i++)
    {
      MultiArray sourceFeelings = socialValues.getElementAtIndex(sources.get(i));
      sum += (Double) sourceFeelings.getElementAtIndex(target);
    }

    return sum / sources.size();
  }

  // returns the altruism between multi sources, one target... uses population to determine
  // the returned value
  public double getAverageAltruismMultipleSourcesWithPopulation(Vector<int[]> sources, int[] target, MultiArray<Integer> pop)
  {
    double ret = 0;
    double totalPop = 0;

    for (int i = 0; i < sources.size(); i++)
    {
      totalPop += pop.getElementAtIndex(sources.get(i));
    }

    for (int i = 0; i < sources.size(); i++)
    {
      int[] currentGroup = sources.get(i);
      MultiArray sourceFeelings = socialValues.getElementAtIndex(currentGroup);
      if (totalPop != 0)
      {
        ret += (Double) sourceFeelings.getElementAtIndex(target) * ((double)(pop.getElementAtIndex(currentGroup)) / totalPop);
      }
    }

    return ret;
  }

  // returns values for multi sources and targets
  public double getAverageAltruismMultipleSourcesAndTargets(Vector<int[]> sources, Vector<int[]> targets)
  {
    double sum = 0;

    for (int i = 0; i < targets.size(); i++)
    {
      sum += getAverageAltruismMultipleSources(sources, targets.get(i));
    }

    return sum / targets.size();
  }

  // like the above method, but scales for population 
  public double getAverageAltruismMultipleSourcesAndTargetsWithPopulation(Vector<int[]> sources, Vector<int[]> targets, MultiArray<Integer> population)
  {
    double sum = 0;

    for (int i = 0; i < targets.size(); i++)
    {
      sum += getAverageAltruismMultipleSourcesWithPopulation(sources, targets.get(i), population);
    }

    return sum / targets.size();
  }

  // sets the value between target and source
  public void setAltruism(int[] source, int[] target, double value)
  {
    MultiArray sourceFeelings = socialValues.getElementAtIndex(source);
    sourceFeelings.setElementAtIndex(target, value);
  }

  // just for testing purposes
  public void setAllToValue(double value)
  {
    for (int i = 0; i < allCombinations.size(); i++)
    {
      int[] source = allCombinations.get(i);
      for (int j = 0; j < allCombinations.size(); j++)
      {
        int[] target = allCombinations.get(j);

        setAltruism(source, target, value);
      }
    }
  }

  // creates a string representing this array
  public String toString()
  {
    //String ret = "";

    // first write the key
    String keyLine = "[";
    for (int i = 0; i < ascriptiveCategories.size() - 1; i++)
    {
      keyLine += ascriptiveCategories.get(i) + ";";
    }
    keyLine += ascriptiveCategories.get(ascriptiveCategories.size() - 1) + "]\n";

    // write the dimensions
    String dimensionLine = "[";
    for (int i = 0; i < dimensions.length - 1; i++)
    {
      dimensionLine += dimensions[i] + ";";
    }
    dimensionLine += dimensions[dimensions.length - 1] + "]\n";

    // write the string names
    String namesLine = "[";
    for (int i = 0; i < stringNames.size() - 1; i++)
    {
      namesLine += stringNames.get(i) + ";";
    }
    namesLine += stringNames.get(stringNames.size() - 1) + "]\n";

    // now fill in the leaves
    for (int i = 0; i < allCombinations.size(); i++)
    {
      int[] source = allCombinations.get(i);
      MultiArray sourceFeelings = socialValues.getElementAtIndex(source);

      for (int j = 0; j < allCombinations.size(); j++)
      {
        String altLine = "";
        int[] target = allCombinations.get(j);
        double alt = (Double) sourceFeelings.getElementAtIndex(target);

        // write the year
        altLine += year + " ";

        // write the source
        altLine += "(";
        Vector<String> sourceStrings = translateIndexIntoStrings(source);
        for (int m = 0; m < sourceStrings.size() - 1; m++)
        {
          altLine += sourceStrings.get(m) + "; ";
        }
        altLine += sourceStrings.get(sourceStrings.size() - 1) + ") ";

        // write the target
        altLine += "(";
        Vector<String> targetStrings = translateIndexIntoStrings(target);
        for (int m = 0; m < targetStrings.size() - 1; m++)
        {
          altLine += targetStrings.get(m) + "; ";
        }
        altLine += targetStrings.get(targetStrings.size() - 1) + ") ";

        // write the value
        altLine += alt;

        // finish this line
        altLine += "\n";
        System.out.println(altLine);
      }
    }

    String ret = keyLine + dimensionLine + namesLine;
    return ret;
  }

  // returns true if successful -- untested
  public boolean writeMatrixToFile(String fileName)
  {
    boolean success = true;
    File altruismFile = new File(fileName);
    System.out.println("size of allcombos: " + allCombinations.size());
    System.out.println("size of allcombos squared: " + allCombinations.size() * allCombinations.size());

    // try to record results
    try
    {
      FileWriter writer = new FileWriter(altruismFile, true);
      PrintWriter out = new PrintWriter(writer);

      out.println(toString());

      // close file
      writer.close();
    } catch (IOException exception)
    {
      System.out.println("IOException in file writing of altruism matrix.");
      return false;
    }

    return success;
  }

  public void printAll()
  {
    // now fill in the leaves
    for (int i = 0; i < allCombinations.size(); i++)
    {
      int[] source = allCombinations.get(i);
      MultiArray sourceFeelings = socialValues.getElementAtIndex(source);

      for (int j = 0; j < allCombinations.size(); j++)
      {
        String altLine = "";
        int[] target = allCombinations.get(j);
        double alt = (Double) sourceFeelings.getElementAtIndex(target);

        // write the year
        altLine += year + " ";

        // write the source
        altLine += "(";
        Vector<String> sourceStrings = translateIndexIntoStrings(source);
        for (int m = 0; m < sourceStrings.size() - 1; m++)
        {
          altLine += sourceStrings.get(m) + "; ";
        }
        altLine += sourceStrings.get(sourceStrings.size() - 1) + ") ";

        // write the target
        altLine += "(";
        Vector<String> targetStrings = translateIndexIntoStrings(target);
        for (int m = 0; m < targetStrings.size() - 1; m++)
        {
          altLine += targetStrings.get(m) + "; ";
        }
        altLine += targetStrings.get(targetStrings.size() - 1) + ") ";

        // write the value
        altLine += alt;
        System.out.println(altLine);
      }
    }
  }
}
